package docs

import (
	"fmt"
	"os"
	"path"
	"strings"

	"github.com/nyaruka/gocommon/dates"
	"github.com/nyaruka/goflow/utils/po"
)

const (
	// documentation extracted from the source code is in this language
	srcLocale = "en_US"
	poDomain  = "flows"
)

// Generator generates a documentation output
type Generator interface {
	Name() string

	// Generate does the actual generation in the given language
	Generate(baseDir, outputDir string, items map[string][]*TaggedItem, gettext func(string) string) error
}

var generators []Generator

// RegisterGenerator registers a documentation generator
func RegisterGenerator(g Generator) {
	generators = append(generators, g)
}

// Generate generates all documentation outputs
func Generate(baseDir, outputDir, localeDir string) error {
	fmt.Println("Processing sources...")

	// extract all documented items from the source code
	taggedItems, err := FindAllTaggedItems(baseDir)
	if err != nil {
		return fmt.Errorf("error extracting tagged items: %w", err)
	}

	for k, v := range taggedItems {
		fmt.Printf(" > Found %d tagged items with tag %s\n", len(v), k)
	}

	locales := po.NewLibrary(localeDir, srcLocale)

	// keep track of all unique strings we look up via gettext
	msgIDs := make(map[string]bool)
	msgUsed := func(msg string) {
		msgIDs[msg] = true
	}

	// generate docs in each supported locale
	for _, language := range locales.Locales() {
		err := generateForLang(baseDir, outputDir, taggedItems, locales, language, msgUsed)
		if err != nil {
			return err
		}
	}

	// use the unique messages set to create a new POT file
	pot := po.NewPO(po.NewHeader("Generated by goflow docgen", dates.Now(), srcLocale))
	for msgID := range msgIDs {
		pot.AddEntry(&po.Entry{MsgID: msgID})
	}
	pot.Sort()

	// and merge with existing locale files
	if err := locales.Update(poDomain, pot); err != nil {
		return fmt.Errorf("error updating locale files: %w", err)
	}

	return nil
}

// generates all documentation a given language by invoking all generators
func generateForLang(baseDir, outputDir string, items map[string][]*TaggedItem, locales *po.Library, locale string, msgUsed func(string)) error {
	fmt.Printf("Generating docs in '%s'\n", locale)

	// en_US -> en-us
	langCode := strings.ToLower(strings.ReplaceAll(locale, "_", "-"))

	// create language specific output directory
	genDir := path.Join(outputDir, langCode)
	os.MkdirAll(genDir, 0777)

	// load PO file for translations
	po, err := locales.Load(locale, poDomain)
	if err != nil {
		return fmt.Errorf("error loading PO file for '%s': %w", locale, err)
	}

	// create gettext function which will keep track of all unique message IDs
	gettext := func(msg string) string {
		if msg != "" {
			msgUsed(msg)
			return po.GetText("", msg)
		}
		return ""
	}

	for _, g := range generators {
		fmt.Printf("Invoking generator: %s...\n", g.Name())

		if err := g.Generate(baseDir, genDir, items, gettext); err != nil {
			return fmt.Errorf("error invoking generator %s: %w", g.Name(), err)
		}
	}

	return nil
}
